/*
 *  Copyright 2019-2020 Zheng Jie
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package me.zhengjie.modules.security.security


import cn.hutool.core.date.DateField
import cn.hutool.core.date.DateUtil
import cn.hutool.core.util.IdUtil
import cn.hutool.crypto.digest.DigestUtil
import io.jsonwebtoken.*
import io.jsonwebtoken.io.Decoders
import io.jsonwebtoken.security.Keys
import io.jsonwebtoken.Claims
import io.jsonwebtoken.JwtBuilder
import io.jsonwebtoken.JwtParser
import io.jsonwebtoken.SignatureAlgorithm
import jakarta.servlet.http.HttpServletRequest
import me.zhengjie.common.utils.RedisUtil
import me.zhengjie.modules.security.config.bean.SecurityProperties
import org.springframework.beans.factory.InitializingBean
import org.springframework.security.core.Authentication
import org.springframework.security.core.userdetails.User
import org.springframework.stereotype.Component
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
import org.springframework.security.core.GrantedAuthority

import java.security.Key
import java.util.*
import java.util.concurrent.TimeUnit

/**
 * @author /
 */
@Component
class TokenProvider(
    private val properties: SecurityProperties,
    private val redisUtils: RedisUtil,
    private var jwtParser: JwtParser? = null,
    private var jwtBuilder: JwtBuilder? = null,
) : InitializingBean {
//    init {
//        this.properties = properties
//        this.redisUtils = redisUtils
//    }

    override fun afterPropertiesSet() {
        val keyBytes: ByteArray = Decoders.BASE64.decode(properties.base64Secret)
        val key: Key = Keys.hmacShaKeyFor(keyBytes)
        jwtParser = Jwts.parser()
            .setSigningKey(key)
            .build()
        jwtBuilder = Jwts.builder()
            .signWith(key, SignatureAlgorithm.HS512)
    }

    /**
     * 创建Token 设置永不过期，
     * Token 的时间有效性转到Redis 维护
     *
     * @param authentication /
     * @return /
     */
    fun createToken(authentication: Authentication): String {
        return jwtBuilder!! // 加入ID确保生成的 Token 都不一致
            .setId(IdUtil.simpleUUID())
            .claim(AUTHORITIES_KEY, authentication.name)
            .setSubject(authentication.name)
            .compact()
    }

    /**
     * 依据Token 获取鉴权信息
     *
     * @param token /
     * @return /
     */
    fun getAuthentication(token: String?): Authentication {
        val claims = getClaims(token)
        val principal = User(claims.subject, "******", ArrayList())
        return UsernamePasswordAuthenticationToken(principal, token, ArrayList<GrantedAuthority>())
    }

    fun getClaims(token: String?): Claims {
        return jwtParser!!
            .parseClaimsJws(token)
            .getBody()
    }

    /**
     * @param token 需要检查的token
     */
    fun checkRenewal(token: String?) {
        // 判断是否续期token,计算token的过期时间
        val loginKey = loginKey(token)
        val time: Long = redisUtils.getExpire(loginKey) * 1000
        val expireDate: Date = DateUtil.offset(Date(), DateField.MILLISECOND, time.toInt())
        // 判断当前时间与过期时间的时间差
        val differ = expireDate.time - System.currentTimeMillis()
        // 如果在续期检查的范围内，则续期
        if (differ <= properties.detect!!) {
            val renew: Long = time + properties.renew!!
            redisUtils.expire(loginKey, renew, TimeUnit.MILLISECONDS)
        }
    }

    fun getToken(request: HttpServletRequest): String? {
        val requestHeader: String = request.getHeader(properties.header)
        return if (requestHeader != null && requestHeader.startsWith(properties.tokenStartWith!!)) {
            requestHeader.substring(7)
        } else null
    }

    /**
     * 获取登录用户RedisKey
     * @param token /
     * @return key
     */
    fun loginKey(token: String?): String {
        val claims = getClaims(token)
        val md5Token = DigestUtil.md5Hex(token)
        return properties.onlineKey + claims.subject + "-" + md5Token
    }

    companion object {
        const val AUTHORITIES_KEY = "user"
    }
}